﻿using System;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Windows.Input;
using DG.Common.Patterns.Commands;
using DG.Common.Patterns.MVVM;
using DG.Common.Validation;

namespace DG.Common.EF.WPF
{
    public class EntityCRUDViewModelBase<TContext, T> : ViewModelBase where T : class, new() where TContext: IDbContext
    {
        public delegate void BeforeSavingDelegate();
        public event BeforeSavingDelegate BeforeSaving;
        
        private T _entity;

        private CRUDOperationType _action;
        private ActionResult<T> _result;
        protected ValidatorCollection Validator = new ValidatorCollection();
        public TContext Context;
        private T _crudViewResult;

        public T Entity
        {
            get { return _entity; }
            set
            {
                if (Equals(value, _entity)) return;
                _entity = value;
                OnPropertyChanged("Entity");
                OnPropertyChanged("");
            }
        }
        public CRUDOperationType Action
        {
            get { return _action; }
            set
            {
                if (value == _action) return;
                _action = value;
                OnPropertyChanged("Action");
            }
        }
        public ActionResult<T> Result
        {
            get { return _result; }
            set
            {
                if (Equals(value, _result)) return;
                _result = value;
                OnPropertyChanged("Result");
            }
        }

        public virtual void SetAction(TContext db, CRUDOperationType acao, T model = null)
        {
            if (acao == CRUDOperationType.Create && model != null)
                throw new ArgumentException(@"model must be null in acao == " + acao, "model");
            if (acao != CRUDOperationType.Create && model == null)
                throw new ArgumentException(@"model cannot null in acao == " + acao, "model");
            
            Context = db;
            Action = acao;
            if (model != null)
            {
                var keys = EntityKeyHelper.Instance.GetKeys(model, db.Context);
                Entity = db.Set<T>().Find(keys);
                if (Entity == null)
                    throw new ArgumentException("Could not find entity for model with keys: " +
                    string.Join(",", keys.Select(s=> s + "").ToArray()));
            }
            else
            {
                Entity = db.Set<T>().Create();
                Context.Context.Entry(Entity).State = EntityState.Added;
            }
            Result = new ActionResult<T>(ResultadoDoFormulario.NotSet, model);
            AfterSetAction();
        }

        protected virtual void AfterSetAction()
        {
            
        }

        protected virtual void BeforeSave()
        {}
        protected virtual void AfterSave()
        {

        }

        public virtual ICommand Save
        {
            get
            {
                return new FunctionalCommand(parameter =>
                {
                    if (!Validator.IsValid())
                    {
                        SendMessage(Validator.GetErrorListMessage());
                        return;
                    }
                    if (BeforeSaving != null) BeforeSaving();
                    BeforeSave();

                    if (Context.Context.GetValidationErrors().Any())
                    {
                        var msg = new StringBuilder();
                        msg.AppendLine("Alguns Erros Foram Detectados");
                        foreach (var entry in Context.Context.GetValidationErrors().GroupBy(e => e.Entry))
                            foreach (var erro in entry.SelectMany(e => e.ValidationErrors))
                            {
                                msg.AppendLine("Entidade: " + entry.Key.Entity +
                                               ", Propriedade: " + erro.PropertyName +
                                               ", Erro: " + erro.ErrorMessage).AppendLine();
                            }
                        SendMessage(msg.ToString());
                        return;
                    }
                    if (Action != CRUDOperationType.Retrive) Context.SaveChanges();
                    AfterSave();
                    Result = new ActionResult<T>(ResultadoDoFormulario.Salvo, Entity);

                    SendMessage("Cadastro realizado com sucesso");

                    CRUDViewResult = Entity;

                    RaiseFinished();
                }, CanSave);
            }
        }

        protected virtual bool CanSave()
        {
            return Action != CRUDOperationType.Retrive;
        }

        public virtual ICommand Cancel
        {
            get
            {
                return new FunctionalCommand(parameter =>
                {
                    Result = new ActionResult<T>(ResultadoDoFormulario.Cancelado, Entity);
                    RaiseFinished();
                });
            }
        }


        public T CRUDViewResult
        {
            get { return _crudViewResult; }
            set
            {
                if (Equals(value, _crudViewResult)) return;
                _crudViewResult = value;
                OnPropertyChanged("CRUDViewResult");
            }
        }
    }
}